![logo](https://github.com/mmattamala/LogosFCFM/blob/master/Ciencias%20de%20la%20Computaci%C3%B3n/Logos%20Facultad/fcfm_dcc_png.png?raw=true =300x)

Informe Minería de Datos

Clasificador de problemas de Programación Competitiva

CC5206 - Introducción a la Minería de Datos

Profesora: Bárbara Poblete

Alumnos: \ Cristián Tamblay \ José Cañete \ Diego Díaz

Auxiliares: \ Hernán Sarmiento \ José Miguel Herrera

Motivación y objetivos principales

Motivación:

¿Cuál es el contexto general del tema/problema/datos que eligió estudiar?

  • El contexto general son las competencias de Programación Competitiva, éstas son competencias en las que programadores deben pensar y resolver un set de problemas de manera correcta (es decir, eficaces y con soluciones óptimas dado el tamaño del input) y en el menor tiempo posible. En ellas, como el nombre lo dice, se compite entre equipos, quedando en mejor lugar aquellos quienes hayan resuelto la mayor cantidad de problemas. En caso de haber varios equipos con la misma cantidad de problemas resueltos, se considerarán mejores aquellos que hayan sido más rápidos programando sus soluciones, es decir, que su tiempo acumulado sea menor. Ahora bien, se trabajará con problemas de estas competencias con el fin de caracterizarlos y clasificarlos. Se intentará separar los problemas en categorías de temas, en particular en las técnicas utilizadas para resolverlas (por ejemplo: programación dinámica y búsqueda binaria) y eventualmente por dificultad del problema. Lo que se intentará es crear un clasificador de estos problemas y en lo posible un recomendador de ellos. Los datos que se eligieron para esta ocasión corresponden a un dataset de problemas de Codeforces.

¿Por qué podría ser de interés estos datos?

  • En primer lugar porque las competencias de Programación Competitiva son muy populares en muchas partes del mundo en contextos en los que se estudia programación, ciencias de la computación y matemáticas. Tanto es así que existen varias competencias a nivel mundial de esto, en particular la IOI a nivel escolar y la ACM ICPC a nivel universitario. Estas competencias tienen un nivel de dificultad alto y para alcanzarlo, estudiantes de todo el mundo se preparan y entrenan constantemente resolviendo problemas. Por otra parte, es conocido que las grandes empresas tech como Google, Microsoft y Facebook entrevistan a sus aspirantes mediante la resolución de problemas de este tipo. Dado ese contexto, el crear un clasificador para de alguna manera facilitar este tipo de entrenamientos, de tal modo que se pueda entrenar o aprender un tópico a la vez, siendo además éste el problema más recomendado de ese tópico a medida que se van resolviendo problemas, puede ser muy útil. Por otra parte, es interesante ver si hay alguna relación entre el texto que explica el problema, y el problema computacional que se quiere resolver. Esto debido a que en general los problemas cuentan una historia, que puede ser muy variada en su temática. Además, se podría tratar de encontrar alguna relación con el nivel de dificultad de éste. Por último, también es interesante notar que si bien los problemas pueden categorizarse de manera individual en clases distintas, también pueden categorizarse en más de una al mismo tiempo. El ejemplo clásico de esto son aquellos problemas más avanzados y difíciles en los que las técnicas más básicas (como búsqueda binaria) se usan de manera constante y sin ser el foco principal del problema.

Temática o problemática central:

¿Qué es lo que les gustaría analizar utilizando estos datos? o ¿Qué problema les gustaría resolver utilizando estos datos?

  • Encontrar relaciones entre el texto que describe el problema (y que se cuenta como una historia en la mayoría de los casos) y el problema computacional a resolver, o al menos a qué tipo (tema) de problema corresponde. Para ello el foco estaría en analizar tres aspectos: el texto que describe el problema, el que describe el input y el que describe el output. También se aprovecharía el hecho que muchos problemas ya están clasificados. Todo esto sería la base para hacer el clasificador de temas y así, eventualmente, poder usar la clasificación anterior en conjunto con las estadísticas de resolución de los problemas, para hacer un clasificador de dificultad y un recomendador de problemas. Una hipótesis inicial en cuanto a la clasificación de temas, es que existen palabras claves que logran describir ciertos temas (por ejemplo, si se menciona "buscar" "máximo" "tal que" "orden" es probable que el problema se resuelva con búsqueda binaria).

¿Cómo se se puede hacer esto de forma preliminar?

  • De forma preliminar, lo que se espera es hacer análisis de texto para encontrar patrones, palabras claves, etc. Para ello, lo primero planteado es hacer un modelo Bag of Words con los textos que describen el problema, el input y el output. Esto con el fin de obtener características y que éstas se puedan procesar de mejor manera. Además de esto, se podrían usar otras técnicas que permitan obtener más características, como lo son el uso de n-gramas o análisis de sentimiento.

Descripción de los datos que se van a utilizar (análisis exploratorio):

  • Las principales características del dataset son:
    • El texto en forma de historia que introduce el problema
    • Las especificaciones del input
    • Las especificaciones del output
    • El límite de memoria
    • El límite de tiempo
    • Los tags asociados

Hipótesis

Se plantean las siguientes hipótesis con respecto al dataset:

  • Es posible predecir los labels/tags de un problema a partir del texto que describe el problema.
  • Es posible predecir los labels/tags de un problema a partir de la forma de entrada y de salida de un problema.
  • Es posible predecir la dificultad de un problema a partir del texto que describe el problema.
  • Es posible predecir la dificultad de un problema a partir de la forma de entrada y de salida de un problema.
  • Algunos labels/tags implican de por sí una dificultad específica en los problemas en la mayoría de los casos y con ello:
    • La cantidad de labels/tags que posee un problema es capaz de determinar la dificultad de éste.
    • El conjunto específico de labels/tags que posee un problema es capaz de determinar la dificultad de éste.
    • Problemas con ciertos labels/tags forman clusters.
    • Problemas por nivel de dificultad forman clusters.

Librerías y Funciones Útiles

Instalación de librerías necesarias para poder visualizar los gráficos que contienen características del dataset

In [0]:
import numpy as np
import pandas as pd
import math
import matplotlib.pyplot as plt
import plotly
import plotly.plotly as py
import plotly.graph_objs as go
plotly.tools.set_credentials_file(username='cristian.tamblay', api_key='OJwr2lNHDmoskz1vzRTb')

def mostrar_todo(df):
    with pd.option_context('display.max_rows', None, 'display.max_columns', None):
        print(df)

Importar Datos

Verificar que output_all.csv esté en el entorno.

Leer/importar datos

In [0]:
datos_problemas = pd.read_csv("output_all.csv",sep=";")
datos_problemas.head()
Out[0]:
contest input_specification letter main_text memory_limit output_specification tags time_limit title
0 1 The input contains three positive integer numb... A Theatre Square in the capital city of Berland ... 256 megabytes Write the needed number of flagstones ['math'] 1 second A Theatre Square
1 1 The first line of the input contains integer n... B In the popular spreadsheets systems for exampl... 64 megabytes Write n lines each line should contain a cell ... ['implementation'/ 'math'] 10 seconds B Spreadsheets
2 1 The input file consists of three lines each of... C Nowadays all circuses in Berland have a round ... 64 megabytes Output the smallest possible area of the ancie... ['geometry'/ 'math'] 2 seconds C Ancient Berland Circus
3 10 The first line contains 6 integer numbers n P1... A Tom is interested in power consumption of his ... 256 megabytes Output the answer to the problem ['implementation'] 1 second A Power Consumption Calculation
4 10 The first line contains two integers N and K 1... B All cinema halls in Berland are rectangles wit... 256 megabytes Output N lines In the i-th line output -1 with... ['dp'/ 'implementation'] 1 second B Cinema Cashier

Preprocesamiento de los datos

Lo primero es eliminar los problemas que no poseen descripción (main_text), pues no son un aporte al momento de llevar a cabo el análisis

In [0]:
datos_problemas = datos_problemas[pd.notnull(datos_problemas['main_text'])]
print(datos_problemas.shape)
(3773, 9)

Luego se arregla el formato de los tags para poder utilizarlos de mejor manera en los experimentos

In [0]:
datos_problemas.tags = datos_problemas.tags.apply(lambda x: [str(elem.strip()).replace("'", "") for elem in x[1:len(x)-1].split('/')])
datos_problemas.tags = datos_problemas.tags.transform(tuple)
datos_problemas = datos_problemas.reset_index(drop=True)

Lo siguiente que se hace es separar el dataset en dos: una parte que posee al menos un tag y otra que no posee tags. Esto pues los problemas con tag aportan más valor en los experimentos a realizar que aquellos sin tag.

In [0]:
datos_notageados = datos_problemas[datos_problemas.tags.apply(lambda x: (len(x) == 1 and '' in x))]
datos_tageados = datos_problemas[datos_problemas.tags.apply(lambda x: not(len(x) == 1 and '' in x))]
datos_tageados = datos_tageados.reset_index(drop=True)

Ahora lo que hará es armar el set de categorías, para luego construir un dataframe separado por éstas

In [0]:
categorias = []
for fila in datos_tageados.tags:
    for cat in fila:
        categorias.append(cat)
categorias = list(set(categorias))

Se crea un nuevo dataframe con columnas de cada categoría, las cuales contienen un 0 o un 1, dependiendo si el texto pertenece a esa categoría en particular o no


In [0]:
columns = ['main_text','tags'] + categorias
#print(columns)
datos_por_categoria = pd.DataFrame(columns=columns)
#datos_por_categoria.head()
datos_por_categoria.main_text = datos_tageados.main_text
datos_por_categoria.tags = datos_tageados.tags

#datos_por_categoria.head()

datos_por_categoria.fillna(0)

for columna in datos_por_categoria.columns:
    #print("columna " + columna)
    if (columna != 'main_text' and columna != 'tags'):
        for index, fila in datos_por_categoria.iterrows():
            #print("index " + str(index))
            #print("fila " + str(fila))
            if (columna in fila['tags']):
                fila[columna] = 1
            else:
                fila[columna] = 0

datos_por_categoria.drop('tags', axis=1, inplace=True)


for categoria in categorias:
  datos_tageados.loc[:,categoria] = datos_por_categoria[categoria]
  
print(datos_por_categoria.shape) 
print(datos_tageados.shape)
print(datos_problemas.shape)
datos_por_categoria.head()
(3670, 37)
(3670, 45)
(3773, 9)
Out[0]:
main_text sortings greedy graphs bitmasks trees probabilities games dp dfs and similar ... 2-sat graph matchings strings data structures two pointers matrices number theory expression parsing flows brute force
0 Theatre Square in the capital city of Berland ... 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
1 In the popular spreadsheets systems for exampl... 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 Nowadays all circuses in Berland have a round ... 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
3 Tom is interested in power consumption of his ... 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
4 All cinema halls in Berland are rectangles wit... 0 0 0 0 0 0 0 1 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 37 columns

Análisis preliminar de los datos

Ahora se crea un nuevo dataframe que se usará para clasificar la dificultad/letra de los problemas. Para esto sólo se usarán los problemas en los que las letras sean del conjunto {A, B, C, D, E, F, G, H}, pues las demás pertenecen en general a concursos especiales y que dificultarían la clasificación.

In [0]:
letras = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
#letras = ["A", 'B', 'C', 'D', 'E']
datos_por_dificultad = datos_tageados[datos_tageados.letter.apply(lambda x: (x in letras))]
datos_por_dificultad = datos_por_dificultad.reset_index(drop=True)
print(datos_por_dificultad.shape)
datos_por_dificultad.head()
(3538, 45)
Out[0]:
contest input_specification letter main_text memory_limit output_specification tags time_limit title sortings ... 2-sat graph matchings strings data structures two pointers matrices number theory expression parsing flows brute force
0 1 The input contains three positive integer numb... A Theatre Square in the capital city of Berland ... 256 megabytes Write the needed number of flagstones (math,) 1 second A Theatre Square 0 ... 0 0 0 0 0 0 0 0 0 0
1 1 The first line of the input contains integer n... B In the popular spreadsheets systems for exampl... 64 megabytes Write n lines each line should contain a cell ... (implementation, math) 10 seconds B Spreadsheets 0 ... 0 0 0 0 0 0 0 0 0 0
2 1 The input file consists of three lines each of... C Nowadays all circuses in Berland have a round ... 64 megabytes Output the smallest possible area of the ancie... (geometry, math) 2 seconds C Ancient Berland Circus 0 ... 0 0 0 0 0 0 0 0 0 0
3 10 The first line contains 6 integer numbers n P1... A Tom is interested in power consumption of his ... 256 megabytes Output the answer to the problem (implementation,) 1 second A Power Consumption Calculation 0 ... 0 0 0 0 0 0 0 0 0 0
4 10 The first line contains two integers N and K 1... B All cinema halls in Berland are rectangles wit... 256 megabytes Output N lines In the i-th line output -1 with... (dp, implementation) 1 second B Cinema Cashier 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 45 columns

In [0]:
cantidad = []
for i in range(0,len(datos_problemas.letter.unique())):
  cantidad.append(len(datos_problemas[datos_problemas.letter == datos_problemas['letter'].unique()[i]]))
cantidadPorLetra = np.column_stack((np.asarray(cantidad),datos_problemas['letter'].unique()))
cantidadPorLetra=cantidadPorLetra[(-cantidadPorLetra[:,0]).argsort()] #El menos es decreasing
trace0 = go.Bar(
            x=cantidadPorLetra[:,1],
            y=cantidadPorLetra[:,0]
    )
data=[trace0]
layout = go.Layout(
            title='Cantidad de problemas en función de la dificultad',
            xaxis=dict(
                type='category',
                title='Letras/Dificultades de los problemas'
            ),
            yaxis=dict(
                title='Cantidad de problemas'
            )
)
fig=go.Figure(data=data, layout=layout)
py.iplot(fig, filename='letra')
Out[0]:

Se analizarán ciertas características del dataset de problemas, partiendo por ver cúanto es el tiempo límite promedio en que deben resolverse.

In [0]:
cantidad = []
for i in range(0,len(datos_problemas.time_limit.unique())):
  cantidad.append(len(datos_problemas[datos_problemas.time_limit == datos_problemas['time_limit'].unique()[i]]))
cantidadPorTiempo = np.column_stack((np.asarray(cantidad),datos_problemas['time_limit'].unique()))
cantidadPorTiempo=cantidadPorTiempo[(-cantidadPorTiempo[:,0]).argsort()] #El menos es decreasing
trace0 = go.Bar(
            x=cantidadPorTiempo[:,1],
            y=cantidadPorTiempo[:,0]
    )
data=[trace0]
layout = go.Layout(
            title='Cantidad de problemas en función del tiempo máximo',
            xaxis=dict(
                type='category',
                title='Tiempo máximo para resolver el problema'
            ),
            yaxis=dict(
                title='Cantidad de problemas'
            )
)
fig=go.Figure(data=data, layout=layout)
py.iplot(fig, filename='tiempo')
Out[0]:

Se observa que la mayor parte de este dataset se divide entre los problemas de 1 y 2 segundos, lo que no nos aporta mucho sobre la clasificación de los problemas. Pueden haber problemas de temas muy distintos con el mismo tiempo de resolución. \ Haciendo el mismo análisis, pero esta vez midiendo la cantidad de memoria que utilizan los problemas, se llega a lo siguiente.

In [0]:
cantidad = []
for i in range(0,len(datos_problemas.memory_limit.unique())):
  cantidad.append(len(datos_problemas[datos_problemas.memory_limit == datos_problemas['memory_limit'].unique()[i]]))
cantidadPorMemoria = np.column_stack((np.asarray(cantidad),datos_problemas['memory_limit'].unique()))
cantidadPorMemoria=cantidadPorMemoria[(-cantidadPorMemoria[:,0]).argsort()] #El menos es decreasing
trace0 = go.Bar(
            x=cantidadPorMemoria[:,1],
            y=cantidadPorMemoria[:,0]
    )
data=[trace0]
layout = go.Layout(
            title='Cantidad de problemas en función de la cantidad máxima de memoria disponible',
            xaxis=dict(
                type='category',
                title='Cantidad máxima de memoria para resolver el problema'
            ),
            yaxis=dict(
                title='Cantidad de problemas'
            )
)
fig=go.Figure(data=data, layout=layout)
py.iplot(fig, filename='memoria')
Out[0]:

Se observa que casi todos los problemas tienen como límite los 256 MB de memoria. No se puede encontrar una rapida relación entre el tipo de problema y la cantidad de memoria entregada.

A continuación se observan los 25 tags más usados.

In [0]:
cantidad = []
posibles = []
for row in datos_problemas.tags:
    for tag in row:
        posibles.append(tag)
posibles = pd.Series(posibles).unique()
for t in range(0,len(posibles)):
  if(posibles[t]==''):
    cantidad.append(len(datos_problemas[datos_problemas.tags.isin((('',),))]))
  else:
    cantidad.append(len(datos_problemas[datos_problemas.tags.apply(lambda x : any((i for i in x if i.find(posibles[t]) >= 0)))]))
cantidadPorTag = np.column_stack((np.asarray(cantidad),np.asarray(posibles)))
cantidadPorTag=cantidadPorTag[(-cantidadPorTag[:,0]).argsort()] #El menos es decreasing
cantidadPorTag=cantidadPorTag[0:25,:]
trace0 = go.Bar(
            x=cantidadPorTag[:,1],
            y=cantidadPorTag[:,0]
    )
data=[trace0]
layout = go.Layout(
            title='Cantidad de problemas en función del tag',
            xaxis=dict(
                type='category',
                title='Tag del problema'
            ),
            yaxis=dict(
                title='Cantidad de problemas'
            )
)
fig=go.Figure(data=data, layout=layout)
py.iplot(fig, filename='tags')
Out[0]:

Se ve que la mayoría de los problemas tiene como tag implementation, lo cual no dice mucho del problema. Adicionalmente, la segunda categoría más popular de problema son aquellos que nisiquiera poseen un tag descriptor, quedando ambigua la naturaleza de éste. Finalmente se puede observar que existen categorías que se repiten, pues están listadas con categorías adicionales, lo cual puede provocar una duplicación de problemas en caso de querer individualizarlos.

Exploración preliminar del texto

En primer lugar, dado que la mayoría de la información se desea obtener del texto (principal, de input y de output) se decidió hacer uso de la librería SKLearn que entre sus variados métodos ofrece la posibilidad de obtener Bag of Words a partir de texto, así como N-Gramas. En este caso particular se decidió probar el uso de Bag of Words de manera sencilla para ver qué tal funciona. Como resultado, con el siguiente código se puede ver el Bag of Words creado, donde cada texto inicial se convierte en un vector y se puede ver el vocabulario con las frecuencias de cada palabra en el conjunto de datos.

Preparación de datos para clasificación de labels

In [0]:
!pip install stopwords
!pip install nltk
import nltk
nltk.download('stopwords')
Collecting stopwords
  Downloading https://files.pythonhosted.org/packages/14/a2/d4eb1d5e609fa0b1d59e929db8eb9ae540f8b2d6db3a4ba26f713e81af15/stopwords-0.1.3.tar.gz (41kB)
    100% |████████████████████████████████| 51kB 1.8MB/s 
Building wheels for collected packages: stopwords
  Running setup.py bdist_wheel for stopwords ... - \ done
  Stored in directory: /root/.cache/pip/wheels/39/fa/c7/c4c5111e658f5c58465d948165dc3395a3c10ff57f4cd20356
Successfully built stopwords
Installing collected packages: stopwords
Successfully installed stopwords-0.1.3
Requirement already satisfied: nltk in /usr/local/lib/python3.6/dist-packages (3.2.5)
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from nltk) (1.11.0)
[nltk_data] Downloading package stopwords to /root/nltk_data...
[nltk_data]   Unzipping corpora/stopwords.zip.
Out[0]:
True

Se procesará la data de tal manera de filtrar aquellas palabras consideradas como stopwords con el propósito de no ensuciar los resultados que se vayan a obtener de los experimentos.

In [0]:
import re
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.dummy import DummyClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import LinearSVC
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
import seaborn as sns
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import cross_validate
from sklearn.metrics import make_scorer, accuracy_score, precision_score, recall_score, f1_score
import warnings
warnings.filterwarnings("ignore")
In [0]:
train, test = train_test_split(datos_por_categoria, test_size=0, shuffle=True)
X_train = train.main_text
X_test = test.main_text
y_train = train.drop(['main_text'], axis=1)
y_test = test.drop(['main_text'], axis=1)

vectorizer = CountVectorizer(stop_words='english',binary=True)

scoring = {'accuracy' : make_scorer(accuracy_score), 
           'precision' : make_scorer(precision_score),
           'recall' : make_scorer(recall_score), 
           'f1_score' : make_scorer(f1_score)}

X_train = vectorizer.fit_transform(X_train).todense()
X_test = vectorizer.transform(X_test).todense()

La clasificación para estos datos se realizará con 1 clasificador por etiqueta. Esta es una buena forma de manejar los problemas que tienen multi-label.

Clasificación de labels

A continuación se llevarán a cabo y se observarán los resultados de diversos métodos de clasificación para luego poder comentar la eficacia a nivel general de cada uno y poder analizar cuáles produjeron mejores y peores resultados. Además, a cada uno de estos se les realizó un 6-fold cross validation.

Clasificación con Naive Bayes

In [0]:
classifiers = []
for i in range(0,36):
  clf = MultinomialNB()
  classifiers.append(clf)
val_acuracy=0
val_precision = 0
val_recall = 0
val_f1_score = 0
for i in range(0,36):
  validacion = cross_validate(clf, X_train, y_train.iloc[:,i].tolist(), cv=5,scoring=scoring)
  val_acuracy += validacion['test_accuracy'].mean()/36.0
  val_precision += validacion['test_precision'].mean()/36.0
  val_recall += validacion['test_recall'].mean()/36.0
  val_f1_score += validacion['test_f1_score'].mean()/36.0
  
print("Accuracy: "+str(val_acuracy))
print("Precision: "+str(val_precision))
print("Recall: "+str(val_recall))
print("F1-Score: "+str(val_f1_score))
Accuracy: 0.9314948126258948
Precision: 0.14740286073766287
Recall: 0.05701815272195064
F1-Score: 0.07958830761094088

Clasificación con SVC

In [0]:
classifiers = []
for i in range(0,36):
  clf = LinearSVC()
  classifiers.append(clf)
val_acuracy=0
val_precision = 0
val_recall = 0
val_f1_score = 0
for i in range(0,36):
  validacion = cross_validate(clf, X_train, y_train.iloc[:,i].tolist(), cv=5,scoring=scoring)
  val_acuracy += validacion['test_accuracy'].mean()/36.0
  val_precision += validacion['test_precision'].mean()/36.0
  val_recall += validacion['test_recall'].mean()/36.0
  val_f1_score += validacion['test_f1_score'].mean()/36.0
  
print("Accuracy: "+str(val_acuracy))
print("Precision: "+str(val_precision))
print("Recall: "+str(val_recall))
print("F1-Score: "+str(val_f1_score))
Accuracy: 0.9231913950934627
Precision: 0.2682798007000631
Recall: 0.16396269686988765
F1-Score: 0.19187052369646063

Clasificación con Regresión Logistica

In [0]:
classifiers = []
for i in range(0,36):
  clf = LogisticRegression(solver='sag')
  classifiers.append(clf)
val_acuracy=0
val_precision = 0
val_recall = 0
val_f1_score = 0
for i in range(0,36):
  validacion = cross_validate(clf, X_train, y_train.iloc[:,i].tolist(), cv=5,scoring=scoring)
  val_acuracy += validacion['test_accuracy'].mean()/36.0
  val_precision += validacion['test_precision'].mean()/36.0
  val_recall += validacion['test_recall'].mean()/36.0
  val_f1_score += validacion['test_f1_score'].mean()/36.0
  
print("Accuracy: "+str(val_acuracy))
print("Precision: "+str(val_precision))
print("Recall: "+str(val_recall))
print("F1-Score: "+str(val_f1_score))
Accuracy: 0.9350739557885548
Precision: 0.30353144656968334
Recall: 0.12123079599008262
F1-Score: 0.16348387152703017

Clasificación con Multi Layer Perceptron

Para este clasificador en particuar se utilizará una red neuronal con capa oculta de 15 neuronas.

In [0]:
classifiers = []
for i in range(0,36):
  clf = MLPClassifier(activation='relu',hidden_layer_sizes=(15,))
  classifiers.append(clf)
val_acuracy=0
val_precision = 0
val_recall = 0
val_f1_score = 0
for i in range(0,36):
  print(i)
  validacion = cross_validate(clf, X_train, y_train.iloc[:,i].tolist(), cv=5,scoring=scoring)
  val_acuracy += validacion['test_accuracy'].mean()/36.0
  val_precision += validacion['test_precision'].mean()/36.0
  val_recall += validacion['test_recall'].mean()/36.0
  val_f1_score += validacion['test_f1_score'].mean()/36.0
print("Accuracy: "+str(val_acuracy))
print("Precision: "+str(val_precision))
print("Recall: "+str(val_recall))
print("F1-Score: "+str(val_f1_score))
Accuracy: 0.9325767084809546
Precision: 0.28486494633001136
Recall: 0.10168165969148982
F1-Score: 0.14001293604342366

Clasificación con Random Forest

In [0]:
classifiers = []
for i in range(0,36):
  clf = RandomForestClassifier()
  classifiers.append(clf)
val_acuracy=0
val_precision = 0
val_recall = 0
val_f1_score = 0
for i in range(0,36):
  validacion = cross_validate(clf, X_train, y_train.iloc[:,i].tolist(), cv=5,scoring=scoring)
  val_acuracy += validacion['test_accuracy'].mean()/36.0
  val_precision += validacion['test_precision'].mean()/36.0
  val_recall += validacion['test_recall'].mean()/36.0
  val_f1_score += validacion['test_f1_score'].mean()/36.0
  
print("Accuracy: "+str(val_acuracy))
print("Precision: "+str(val_precision))
print("Recall: "+str(val_recall))
print("F1-Score: "+str(val_f1_score))
Accuracy: 0.9390480539150667
Precision: 0.2664979719034941
Recall: 0.025613603106694798
F1-Score: 0.04509918186334653

Clasificación con Dummy

In [0]:
classifiers = []
for i in range(0,36):
  clf = DummyClassifier(strategy='uniform')
  classifiers.append(clf)
val_acuracy=0
val_precision = 0
val_recall = 0
val_f1_score = 0
for i in range(0,36):
  validacion = cross_validate(clf, X_train, y_train.iloc[:,i].tolist(), cv=5,scoring=scoring)
  val_acuracy += validacion['test_accuracy'].mean()/36.0
  val_precision += validacion['test_precision'].mean()/36.0
  val_recall += validacion['test_recall'].mean()/36.0
  val_f1_score += validacion['test_f1_score'].mean()/36.0
  
print("Accuracy: "+str(val_acuracy))
print("Precision: "+str(val_precision))
print("Recall: "+str(val_recall))
print("F1-Score: "+str(val_f1_score))
Accuracy: 0.4959955566841822
Precision: 0.060891652247702745
Recall: 0.5021415864296219
F1-Score: 0.09801093908731716

Al concluir esta ronda inicial de experimentos se puede observar que aquel con mejor precisión fue el clasificador de Naïve Bayes con una precisión de 46%. En cuanto al peor método, se incluyó el clasificador Dummy con el propósito de exponer lo ineficiente que es como clasificador, logrando una precisión de tan sólo un 5%. Sin embargo, se esperaba que los resultados de este último clasificador fueran malos, por tanto si se analizan los resultados de los otros clasificadores usados, el peor corresponde en realidad al clasificador SVC, con una precisión de tan sólo 33%.

Preparación de datos para clasificación de dificultad basada en texto principal

In [0]:
datos_clasificacion_dificultad = pd.DataFrame(columns=['main_text', 'letter'])
datos_clasificacion_dificultad.main_text = datos_por_dificultad.main_text
datos_clasificacion_dificultad.letter = datos_por_dificultad.letter

datos_clasificacion_dificultad = datos_clasificacion_dificultad[pd.notnull(datos_clasificacion_dificultad['letter'])]

train, test = train_test_split(datos_clasificacion_dificultad, test_size=0.33, shuffle=True)
X_train = train.main_text
X_test = test.main_text
y_train = train.drop(['main_text'], axis=1).letter.ravel()
y_test = test.drop(['main_text'], axis=1).letter.ravel()

vectorizer = CountVectorizer(stop_words='english')
#vectorizer = TfidfVectorizer(stop_words='english')

X_train = vectorizer.fit_transform(X_train).todense()
X_test = vectorizer.transform(X_test).todense()

Clasificación de dificultad basada en texto principal

In [0]:
### COMPLETAR ESTE CÓDIGO

## run_classifier recibe un clasificador, un dataset (X, y) 
## y opcionalmente la cantidad de resultados que se quiere obtener del clasificador

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score, recall_score, precision_score


def run_classifier(clf, X_train, X_test, y_train, y_test, num_tests=100):
    metrics = {'f1-score': [], 'precision': [], 'recall': []}
    
    for _ in range(num_tests):
        clf.fit(X_train, y_train)
        predictions = clf.predict(X_test)
      
        ### FIN COMPLETAR ACÁ
        
        
        metrics['f1-score'].append(f1_score(y_test, predictions, average='micro'))  # X_test y y_test deben ser definidos previamente
        metrics['recall'].append(recall_score(y_test, predictions, average=None))
        metrics['precision'].append(precision_score(y_test, predictions, average='micro'))
    
    return metrics
In [0]:
## ejecutar este código

from sklearn.svm import SVC  # support vector machine classifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB  # naive bayes
from sklearn.neighbors import KNeighborsClassifier
from sklearn.dummy import DummyClassifier
from sklearn.multioutput import MultiOutputClassifier
from sklearn.naive_bayes import BernoulliNB
from sklearn.tree import ExtraTreeClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.semi_supervised import LabelPropagation
from sklearn.semi_supervised import LabelSpreading
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.svm import LinearSVC
from sklearn.linear_model import LogisticRegression
from sklearn.linear_model import LogisticRegressionCV
from sklearn.neural_network import MLPClassifier
from sklearn.neighbors import NearestCentroid
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
from sklearn.neighbors import RadiusNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import RidgeClassifier
from sklearn.linear_model import RidgeClassifierCV

c0 = ("Base Dummy", DummyClassifier())
c1 = ("Decision Tree", DecisionTreeClassifier())
c2 = ("Gaussian Naive Bayes", GaussianNB())
c3 = ("KNN", KNeighborsClassifier(n_neighbors=8))
c4 = ("Bernoulli Naive Bayes", BernoulliNB())
c5 = ("Extra Tree", ExtraTreeClassifier())
c6 = ("Extra Trees", ExtraTreesClassifier())
c7 = ("Label Propagation", LabelPropagation())
c8 = ("Label Spreading", LabelSpreading())
c9 = ("Linear Discriminant Analysis", LinearDiscriminantAnalysis())
c10 = ("Linear SVC", LinearSVC())
#c11 = ("Logistic Regression", LogisticRegression(multi_class="multinomial", solver="saga"))
#c12 = ("Logistic Regression CV", LogisticRegressionCV(multi_class="multinomial", solver="saga"))
c13 = ("Multi Layer Perceptron", MLPClassifier())
c14 = ("Nearest Centroid", NearestCentroid())
c15 = ("Quadratic Discriminant Analysis", QuadraticDiscriminantAnalysis())
#c16 = ("Radius Neighbors", RadiusNeighborsClassifier())
c17 = ("Random Forest", RandomForestClassifier())
c18 = ("Ridge Classifier", RidgeClassifier())
c19 = ("Ridge ClassifierCV", RidgeClassifierCV())


classifiers = [c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c13, c14, c15, c17, c18, c19]

for name, clf in classifiers:
    metrics = run_classifier(clf, X_train, X_test, y_train, y_test, num_tests=1)   # hay que implementarla en el bloque anterior.
    print("----------------")
    print("Resultados para clasificador: ",name) 
    print("Precision promedio:",np.array(metrics['precision']).mean())
    print("Recall promedio:",np.array(metrics['recall']).mean())
    print("F1-score promedio:",np.array(metrics['f1-score']).mean())
    print("----------------\n\n")
    
print("FIN")
----------------
Resultados para clasificador:  Base Dummy
Precision promedio: 0.16267123287671234
Recall promedio: 0.12843304838516723
F1-score promedio: 0.16267123287671234
----------------


----------------
Resultados para clasificador:  Decision Tree
Precision promedio: 0.20804794520547945
Recall promedio: 0.14329086368730926
F1-score promedio: 0.20804794520547945
----------------


----------------
Resultados para clasificador:  Gaussian Naive Bayes
Precision promedio: 0.18065068493150685
Recall promedio: 0.12802685975804723
F1-score promedio: 0.18065068493150685
----------------


----------------
Resultados para clasificador:  KNN
Precision promedio: 0.19263698630136986
Recall promedio: 0.12225712451871128
F1-score promedio: 0.19263698630136986
----------------


----------------
Resultados para clasificador:  Bernoulli Naive Bayes
Precision promedio: 0.2534246575342466
Recall promedio: 0.16081917056927494
F1-score promedio: 0.2534246575342466
----------------


----------------
Resultados para clasificador:  Extra Tree
Precision promedio: 0.21232876712328766
Recall promedio: 0.14962510175724458
F1-score promedio: 0.21232876712328763
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/ensemble/forest.py:246: FutureWarning:

The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.

----------------
Resultados para clasificador:  Extra Trees
Precision promedio: 0.2422945205479452
Recall promedio: 0.15902205484289259
F1-score promedio: 0.2422945205479452
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/semi_supervised/label_propagation.py:205: RuntimeWarning:

invalid value encountered in true_divide

----------------
Resultados para clasificador:  Label Propagation
Precision promedio: 0.20976027397260275
Recall promedio: 0.12547284621903443
F1-score promedio: 0.20976027397260275
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/semi_supervised/label_propagation.py:205: RuntimeWarning:

invalid value encountered in true_divide

----------------
Resultados para clasificador:  Label Spreading
Precision promedio: 0.2089041095890411
Recall promedio: 0.12488873406950173
F1-score promedio: 0.2089041095890411
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:388: UserWarning:

Variables are collinear.

----------------
Resultados para clasificador:  Linear Discriminant Analysis
Precision promedio: 0.18493150684931506
Recall promedio: 0.1415483322652549
F1-score promedio: 0.18493150684931509
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/svm/base.py:922: ConvergenceWarning:

Liblinear failed to converge, increase the number of iterations.

----------------
Resultados para clasificador:  Linear SVC
Precision promedio: 0.19863013698630136
Recall promedio: 0.1407910675951926
F1-score promedio: 0.19863013698630133
----------------


----------------
Resultados para clasificador:  Multi Layer Perceptron
Precision promedio: 0.20633561643835616
Recall promedio: 0.14529809949852088
F1-score promedio: 0.20633561643835616
----------------


----------------
Resultados para clasificador:  Nearest Centroid
Precision promedio: 0.2465753424657534
Recall promedio: 0.17618179700662787
F1-score promedio: 0.2465753424657534
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

----------------
Resultados para clasificador:  Quadratic Discriminant Analysis
Precision promedio: 0.0761986301369863
Recall promedio: 0.10854667836306232
F1-score promedio: 0.0761986301369863
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/ensemble/forest.py:246: FutureWarning:

The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.

----------------
Resultados para clasificador:  Random Forest
Precision promedio: 0.2551369863013699
Recall promedio: 0.16632077630072964
F1-score promedio: 0.2551369863013699
----------------


----------------
Resultados para clasificador:  Ridge Classifier
Precision promedio: 0.2029109589041096
Recall promedio: 0.1387200252220836
F1-score promedio: 0.20291095890410957
----------------


----------------
Resultados para clasificador:  Ridge ClassifierCV
Precision promedio: 0.2046232876712329
Recall promedio: 0.13643532676595038
F1-score promedio: 0.2046232876712329
----------------


FIN

Preparación de datos para clasificacion de dificultad basada en tags

In [0]:
columns = ['letter'] + categorias
datos_clasificacion_dificultad = pd.DataFrame(columns=columns)
datos_clasificacion_dificultad.letter = datos_por_dificultad.letter

for categoria in categorias:
  datos_clasificacion_dificultad[categoria] = datos_por_dificultad[categoria]

datos_clasificacion_dificultad = datos_clasificacion_dificultad[pd.notnull(datos_clasificacion_dificultad['letter'])]
datos_clasificacion_dificultad = datos_clasificacion_dificultad.reset_index(drop=True)

  
train, test = train_test_split(datos_clasificacion_dificultad, test_size=0.33, shuffle=True)
y_train = train.letter.ravel()
y_test = test.letter.ravel()
X_train = train.drop(['letter'], axis=1)
X_test = test.drop(['letter'], axis=1)

Clasificacion de datos para clasificacion de dificultad basada en tags

In [0]:
## ejecutar este código


from sklearn.svm import SVC  # support vector machine classifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB  # naive bayes
from sklearn.neighbors import KNeighborsClassifier
from sklearn.dummy import DummyClassifier
from sklearn.multioutput import MultiOutputClassifier
from sklearn.naive_bayes import BernoulliNB
from sklearn.tree import ExtraTreeClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.semi_supervised import LabelPropagation
from sklearn.semi_supervised import LabelSpreading
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.svm import LinearSVC
from sklearn.linear_model import LogisticRegression
from sklearn.linear_model import LogisticRegressionCV
from sklearn.neural_network import MLPClassifier
from sklearn.neighbors import NearestCentroid
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
from sklearn.neighbors import RadiusNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import RidgeClassifier
from sklearn.linear_model import RidgeClassifierCV

c0 = ("Base Dummy", DummyClassifier())
c1 = ("Decision Tree", DecisionTreeClassifier())
c2 = ("Gaussian Naive Bayes", GaussianNB())
c3 = ("KNN", KNeighborsClassifier(n_neighbors=8))
c4 = ("Bernoulli Naive Bayes", BernoulliNB())
c5 = ("Extra Tree", ExtraTreeClassifier())
c6 = ("Extra Trees", ExtraTreesClassifier())
c7 = ("Label Propagation", LabelPropagation())
c8 = ("Label Spreading", LabelSpreading())
c9 = ("Linear Discriminant Analysis", LinearDiscriminantAnalysis())
c10 = ("Linear SVC", LinearSVC())
#c11 = ("Logistic Regression", LogisticRegression(multi_class="multinomial", solver="saga"))
#c12 = ("Logistic Regression CV", LogisticRegressionCV(multi_class="multinomial", solver="saga"))
c13 = ("Multi Layer Perceptron", MLPClassifier())
c14 = ("Nearest Centroid", NearestCentroid())
c15 = ("Quadratic Discriminant Analysis", QuadraticDiscriminantAnalysis())
#c16 = ("Radius Neighbors", RadiusNeighborsClassifier())
c17 = ("Random Forest", RandomForestClassifier())
c18 = ("Ridge Classifier", RidgeClassifier())
c19 = ("Ridge ClassifierCV", RidgeClassifierCV())


classifiers = [c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c13, c14, c15, c17, c18, c19]

for name, clf in classifiers:
    metrics = run_classifier(clf, X_train, X_test, y_train, y_test, num_tests=10)   # hay que implementarla en el bloque anterior.
    print("----------------")
    print("Resultados para clasificador: ",name) 
    print("Precision promedio:",np.array(metrics['precision']).mean())
    print("Recall promedio:",np.array(metrics['recall']).mean())
    print("F1-score promedio:",np.array(metrics['f1-score']).mean())
    print("----------------\n\n")
    
print("FIN")
----------------
Resultados para clasificador:  Base Dummy
Precision promedio: 0.1764554794520548
Recall promedio: 0.1266443333795518
F1-score promedio: 0.1764554794520548
----------------


----------------
Resultados para clasificador:  Decision Tree
Precision promedio: 0.3033390410958904
Recall promedio: 0.1922861758412824
F1-score promedio: 0.3033390410958904
----------------


----------------
Resultados para clasificador:  Gaussian Naive Bayes
Precision promedio: 0.018835616438356163
Recall promedio: 0.15508878822641167
F1-score promedio: 0.018835616438356163
----------------


----------------
Resultados para clasificador:  KNN
Precision promedio: 0.3150684931506849
Recall promedio: 0.1979946808010084
F1-score promedio: 0.3150684931506849
----------------


----------------
Resultados para clasificador:  Bernoulli Naive Bayes
Precision promedio: 0.3167808219178082
Recall promedio: 0.2035589596454414
F1-score promedio: 0.3167808219178082
----------------


----------------
Resultados para clasificador:  Extra Tree
Precision promedio: 0.304195205479452
Recall promedio: 0.19547986012301666
F1-score promedio: 0.304195205479452
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/ensemble/forest.py:246: FutureWarning:

The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.

----------------
Resultados para clasificador:  Extra Trees
Precision promedio: 0.3053082191780822
Recall promedio: 0.19614758680204758
F1-score promedio: 0.3053082191780822
----------------


----------------
Resultados para clasificador:  Label Propagation
Precision promedio: 0.3082191780821918
Recall promedio: 0.19898778213291807
F1-score promedio: 0.3082191780821918
----------------


----------------
Resultados para clasificador:  Label Spreading
Precision promedio: 0.2996575342465753
Recall promedio: 0.19371631951973528
F1-score promedio: 0.2996575342465753
----------------


----------------
Resultados para clasificador:  Linear Discriminant Analysis
Precision promedio: 0.2988013698630137
Recall promedio: 0.23144782944361508
F1-score promedio: 0.2988013698630137
----------------


----------------
Resultados para clasificador:  Linear SVC
Precision promedio: 0.30222602739726023
Recall promedio: 0.19585363368370498
F1-score promedio: 0.30222602739726023
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

/usr/local/lib/python3.6/dist-packages/sklearn/neural_network/multilayer_perceptron.py:562: ConvergenceWarning:

Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

----------------
Resultados para clasificador:  Multi Layer Perceptron
Precision promedio: 0.3117294520547945
Recall promedio: 0.21125400064462463
F1-score promedio: 0.3117294520547945
----------------


----------------
Resultados para clasificador:  Nearest Centroid
Precision promedio: 0.23972602739726026
Recall promedio: 0.24136908900601445
F1-score promedio: 0.23972602739726026
----------------


/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/discriminant_analysis.py:692: UserWarning:

Variables are collinear

/usr/local/lib/python3.6/dist-packages/sklearn/ensemble/forest.py:246: FutureWarning:

The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.

----------------
Resultados para clasificador:  Quadratic Discriminant Analysis
Precision promedio: 0.09845890410958905
Recall promedio: 0.1544730905207259
F1-score promedio: 0.09845890410958905
----------------


----------------
Resultados para clasificador:  Random Forest
Precision promedio: 0.3050513698630137
Recall promedio: 0.1997235276561123
F1-score promedio: 0.3050513698630137
----------------


----------------
Resultados para clasificador:  Ridge Classifier
Precision promedio: 0.30393835616438364
Recall promedio: 0.19732230505204942
F1-score promedio: 0.30393835616438364
----------------


----------------
Resultados para clasificador:  Ridge ClassifierCV
Precision promedio: 0.30308219178082185
Recall promedio: 0.19433144372212574
F1-score promedio: 0.30308219178082185
----------------


FIN

Conclusiones

Para concluir, nos concentraremos en comparar precision y F1 score, ya que nuestro foco es qué tan bien se clasifican los positivos y además cuántos de los positivos fueron ignorados.

En el caso de clasificar los tags por el 'main_text', el clasificador SVC obtiene el mejor rendimiento. Por el contrario, el que tiene el peor rendimiento es Naïve Bayes, pese a que tiene la precisión más alta (hace pocos intentos pero buenos).

Continuando con el caso de la dificultad en base al texto, el mejor clasificador es el basado en Random Forest, aunque la mayoría de los clasificadores están al nivel del Dummy.

Terminando con el caso de la dificultad en base a los tags, el mejor clasificador es Bernoulli Naïve Bayes.